//
// PomKnob
//
// Copyright (C) jextra.net.
//
//  This file is part of the net.jextra.crackdep Library.
//
//  The net.jextra.crackdep Library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The net.jextra.crackdep Library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the net.jextra.crackdep Library; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.crackdep;

import java.io.*;
import java.security.*;
import java.util.*;
import net.jextra.crack.*;
import net.jextra.crack.tool.*;
import net.jextra.dep.*;
import net.jextra.dep.pom.*;
import net.jextra.dep.repo.*;

/**
 * <p>
 * These are basic commands for a "dependency-aware" component.
 * </p>
 */
public class PomKnob
{
    // ============================================================
    // Fields
    // ============================================================

    private PomKnobParams params;

    // ============================================================
    // Constructors
    // ============================================================

    public PomKnob( PomKnobParams params )
    {
        this.params = params;
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    @CrackCommand( description = "Parse the dependency-definition file and output the component dependency tree." )
    public void tree()
        throws Exception
    {
        DepTreePrinter printer = new DepTreePrinter();
        printer.print( params.getDepTree( "compile", "provided" ), System.out );
    }

    @CrackCommand( description = "Same as 'tree' command except shows all jars." )
    public void treeVerbose()
        throws Exception
    {
        DepTreePrinter printer = new DepTreePrinter();
        printer.setShowMissingComponents( true );
        printer.print( params.getDepTree( "compile", "provided", "test", "runtime", "system" ), System.out );
    }

    @CrackCommand( visible = false,
        description = "Output the component dependency tree as if using external store as local repo." )
    public void treeTeam()
        throws Exception
    {
        DepTreePrinter printer = new DepTreePrinter();
        printer.print( params.getTeamDepTree(), System.out );
    }

    @CrackCommand( description = "Parse the depDef file and output the dependency classpath." )
    public void classpath()
        throws Exception
    {
        System.out.println( params.getClasspath() );
    }

    @CrackCommand( description = "Get any components needed from the team-repos and copy them to the local-repo." )
    public void update()
        throws Exception
    {
        DepTreeSearcher searcher = new DepTreeSearcher( params.getTeamDepTree() );

        int numAcquired = 0;
        for ( DepComponent component : searcher.getDependencies() )
        {
            int count = copyComponentToLocalRepo( component );
            numAcquired += count;
        }

        if ( numAcquired > 0 )
        {
            System.out.printf( "\n%d files have been acquired.\n", numAcquired );
        }
        else
        {
            System.out.println( "\nEverything is already up-to-date.\n" );
        }
    }

    /* @CrackCommand( description = "Prepare build directory to send to repo.", visible = false )
     public void mavenfyBuild()
         throws Exception
     {
         //
         // Rename .jar and .src.zip
         //
         File oldFile = new File( params.getBuildDir(), params.getBuildName() + ".jar" );
         if ( oldFile.exists() )
         {
             File newFile = new File( params.getBuildDir(), String.format( "%s-%s%s",
                 getArtifactId( params.getComponentName() ), params.getVersionNumber(), ".jar" ) );
             newFile.delete();
             System.out.printf( "Renaming [%s] to [%s]\n", oldFile.getAbsolutePath(), newFile.getAbsolutePath() );
             oldFile.renameTo( newFile );
         }

         oldFile = new File( params.getBuildDir(), params.getBuildName() + "-sources.zip" );
         if ( oldFile.exists() )
         {
             File newFile = new File( params.getBuildDir(), String.format( "%s-%s%s",
                 getArtifactId( params.getComponentName() ), params.getVersionNumber(), "-sources.jar" ) );
             newFile.delete();
             System.out.printf( "Renaming [%s] to [%s]\n", oldFile.getAbsolutePath(), newFile.getAbsolutePath() );
             oldFile.renameTo( newFile );
         }

         oldFile = new File( params.getBuildDir(), params.getBuildName() + "-javadoc.zip" );
         if ( oldFile.exists() )
         {
             File newFile = new File( params.getBuildDir(), String.format( "%s-%s%s",
                 getArtifactId( params.getComponentName() ), params.getVersionNumber(), "-javadoc.jar" ) );
             newFile.delete();
             System.out.printf( "Renaming [%s] to [%s]\n", oldFile.getAbsolutePath(), newFile.getAbsolutePath() );
             oldFile.renameTo( newFile );
         }

         //
         // Copy pom.xml file to build directory.
         //
         File pomFile = params.getSelfPomFile();
         File newPomFile = new File( params.getBuildDir(), String.format( "%s-%s%s",
             getArtifactId( params.getComponentName() ), params.getVersionNumber(), PomReader.POM_EXT ) );
         FileCopier copier = new FileCopier();
         copier.copyFile( pomFile, newPomFile );

         //
         // Create SHA1 files
         //
         System.out.printf( "Creating MD5 and SHA1 files\n" );
         FileSet fileSet = new StrainedFileSet( params.getBuildDir(), new RegexFileStrainer().exclude( ".*\\.sha1" )
             .exclude( ".*\\.md5" ) );
         for ( File file : fileSet )
         {
             //            createChecksum( file, "MD5", ".md5" );
             //            createChecksum( file, "SHA1", ".sha1" );
         }
     }*/

    @CrackCommand( description = "Copy build to local-store." )
    public void release()
        throws Exception
    {
        assert params.getBuildDir() != null : "getBuildSubDir can not be null.";
        assert params.getLocalRepo() != null : "getLocalRepo can not be null.";

        CrackBuild.getInstance().invokeCommand( "build" );
        //        CrackBuild.getInstance().invokeKnobCommand( this, "mavenfyBuild" );

        DepRepoUploader uploader = new DepRepoUploader( params.getLocalRepo() );

        //
        // Upload pom.xml file to repo.
        //
        File pomFile = params.getSelfPomFile();
        String pomName = String.format( "%s-%s%s", getArtifactId( params.getComponentName() ),
            params.getVersionNumber(), PomReader.POM_EXT );
        uploader.uploadFile( pomFile, params.getComponentName(), params.getVersionNumber(), pomName );

        File md5File = File.createTempFile( "crack", ".md5" );
        createChecksum( pomFile, md5File, "MD5", pomName );
        uploader.uploadFile( md5File, params.getComponentName(), params.getVersionNumber(), pomName + ".md5" );
        md5File.delete();

        File sha1File = File.createTempFile( "crack", ".sha1" );
        createChecksum( pomFile, sha1File, "SHA1", pomName );
        uploader.uploadFile( sha1File, params.getComponentName(), params.getVersionNumber(), pomName + ".sha1" );
        sha1File.delete();

        //
        // Upload jar file to repo.
        //
        String jarName = String.format( "%s-%s%s", getArtifactId( params.getComponentName() ),
            params.getVersionNumber(), ".jar" );
        File jarFile = new File( params.getBuildDir(), params.getBuildName() + ".jar" );
        uploader.uploadFile( jarFile, params.getComponentName(), params.getVersionNumber(), jarName );

        md5File = File.createTempFile( "crack", ".md5" );
        createChecksum( jarFile, md5File, "MD5", jarName );
        uploader.uploadFile( md5File, params.getComponentName(), params.getVersionNumber(), jarName + ".md5" );
        md5File.delete();

        sha1File = File.createTempFile( "crack", ".sha1" );
        createChecksum( jarFile, sha1File, "SHA1", jarName );
        uploader.uploadFile( sha1File, params.getComponentName(), params.getVersionNumber(), jarName + ".sha1" );
        sha1File.delete();

        //
        // Create SHA1 files
        //
        //        System.out.printf( "Creating MD5 and SHA1 files\n" );
        //        FileSet fileSet = new StrainedFileSet( params.getBuildSubDir(), new RegexFileStrainer().exclude( ".*\\.sha1" )
        //            .exclude( ".*\\.md5" ) );
        //        for ( File file : fileSet )
        //        {
        //            createChecksum( file, "MD5", ".md5" );
        //            createChecksum( file, "SHA1", ".sha1" );
        //        }

        //
        // Copy all build files
        //
        //        FileSet fileSet = new StrainedFileSet( params.getBuildSubDir() );
        //        for ( File file : fileSet )
        //        {
        //            uploader.uploadFile( file, params.getComponentName(), params.getVersionNumber(), "/" + file.getName() );
        //        }
        //        
        //        uploader.uploadFile( file, params.getComponentName(), params.getVersionNumber(), "/" + file.getName() );
        //        uploader.uploadFile( file, params.getComponentName(), params.getVersionNumber(), "/" + file.getName() );
        //        uploader.uploadFile( file, params.getComponentName(), params.getVersionNumber(), "/" + file.getName() );
    }

    @CrackCommand( description = "Copy build to given team-store." )
    public void publish( @CrackArg( value = "teamRepo",
        description = "Name of team-repo specified in crack/conf/dep-conf.xml to upload to." ) String teamRepo )
        throws Exception
    {
        assert params.getBuildDir() != null : "getBuildSubDir can not be null.";
        assert teamRepo != null : "teamRepo arg can not be null.";
        assert params.getTeamRepo( teamRepo ) != null : "teamRepo [" + teamRepo + "] could not be found.";

        CrackBuild.getInstance().invokeCommand( "build" );
        CrackBuild.getInstance().invokeKnobCommand( this, "mavenfyBuild" );

        DepRepoUploader uploader = new DepRepoUploader( params.getTeamRepo( teamRepo ) );

        //
        // Upload all build files
        //
        uploader.uploadDir( params.getBuildDir(), params.getComponentName(), params.getVersionNumber(), null );
    }

    // ----------
    // private
    // ----------

    public static String getArtifactId( String name )
    {
        int slash = name.lastIndexOf( '/' );

        return name.substring( slash + 1 );
    }

    private void createChecksum( File inFile, File shaFile, String type, String name )
        throws NoSuchAlgorithmException, IOException
    {
        MessageDigest algorithm = MessageDigest.getInstance( type );

        BufferedInputStream in = new BufferedInputStream( new FileInputStream( inFile ) );
        DigestInputStream dis = new DigestInputStream( in, algorithm );

        // read the file and update the hash calculation
        while ( dis.read() != -1 )
        {
        }

        in.close();
        dis.close();

        // get the hash value as byte array
        byte[] hash = algorithm.digest();

        Formatter formatter = new Formatter();
        for ( byte b : hash )
        {
            formatter.format( "%02x", b );
        }

        String hashString = formatter.toString();

        PrintWriter out = new PrintWriter( shaFile );
        out.print( hashString + "  " + name );
        out.close();
    }

    private int copyComponentToLocalRepo( DepComponent component )
        throws Exception
    {
        return copyDirToLocalRepo( component, "" );
    }

    private int copyDirToLocalRepo( DepComponent component, String path )
        throws Exception
    {
        int count = 0;
        for ( DepRepoFile re : component.getRepo().getFileEntries( component.getName(), component.getVersionNumber(),
            path ) )
        {
            if ( re.isDirectory() )
            {
                copyDirToLocalRepo( component, re.getPath() );
            }
            else
            {
                if ( copyFileToLocalRepo(
                    component.getRepo().getFileEntryInputStream( re.getComponentName(), re.getVersionNumber(),
                        re.getPath() ), re.getComponentName(), re.getVersionNumber(), re.getPath() ) )
                {
                    count++;
                }
            }
        }

        return count;
    }

    private boolean copyFileToLocalRepo( InputStream in, String componentName, String versionNumber, String path )
        throws Exception
    {
        File toDir = params.getLocalRepo().getComponentVersionDir( componentName, versionNumber );
        File to = new File( toDir, path );

        // If the file already exists, assume that it is up-to-date.
        if ( to.exists() )
        {
            return false;
        }

        to.getParentFile().mkdirs();
        try
        {
            FileCopier copier = new FileCopier();
            copier.copyStream( in, to );
        }
        catch ( FileNotFoundException ex )
        {
            System.err.printf( "Source InputStream [%s/%s/%s] does not exist.\n", componentName, versionNumber, path );
            return false;
        }

        return true;
    }
}
